Notion で作成されたページを日時でサマリしてSlack に投稿してみた
こんにちわ。西田@CX事業本部です
今回は Notion に投稿された複数のページのサマリを日時で Slack に投稿するアプリを作ってみました
全体の構成
CloudWatch Events で決まった時間に Lambda を起動し、その Lambda の中で Notion からデータを取得し、Slack にサマリを投稿します
背景
Notion のページの更新を Slack に通知する連携はすでにありますが、更新のたびに通知がきてしまうと、通知が多くなり過ぎてしまうケースもあります
そのため、前日に任意のデータベースに作成された Page のタイトルとリンクをまとめて Slack に投稿するアプリを作っていきたいと思います
前提
この記事は以下の前提で書かれています
- Node.js がインストールされてる
- AWSのアカウントを持っている
- aws cli がインストールされてる
Slack アプリケーション を作成する
Slack に投稿するのに Slack API を使用します。Slack API を使用するには Slack アプリを登録する必要があります
以下のページを開き「Create new App」をクリックします
Slack API: Applications | Slack
「From Scrach」を選択し
任意の名前と、インストール先のワークスペースを選択し、Slack アプリを作成します
作成されたら左メニューの 「OAuth & Permissions」を選ん権限を付与する画面を開きます
Scopes > Bot Token Scopes で chat:write
権限を付与します
最後にワークスペースにアプリを登録して、その後に表示されるトークンを保存して完成です
Notion Integration を作成する
Notion データベースからデータのデータを読み込むには Notion Integration を作成する必要があります
連携する Notion Workspace にログインした状態で以下のページにアクセスします
My integrations | Notion Developers
「New Integration」を押します
今回必要なのは Notion データベースへの投稿なので、「Reacd Content」のみ権限を設定します
「Secrets」 の 「Show」 を押して内容を表示させ後ほど使うのでコピーしておきます
Notion Integration から Notion データベースへの接続を許可
通常の Notion を開き、Slackにサマリを投稿する対象の Notionデータベースのメニューから「Add Connections」を選び今回追加した Integration を選択し、Integration からデータベースにアクセスできるようにします
Notion データベースのIDを取得する
データベースのページのIDを取得します。少しわかりにくいですが、データベースのページにアクセスした時のURLのパス部分がIDになります
[https://notion.so/${ここがID}?v=xxx](https://notion.so/${ここがID}?v=xxx)
CDKで Lambda をデプロイする環境を構築
cdk コマンドでプロジェクトを作成します
cdk init --language typescript
作成されたプロジェクトのディレクトリで必要な npm パッケージを追加します
npm install @slack/web-api @notionhq/client @types/aws-lambda
Lambd で使用するパラメーターをSSMに登録します
Lambda で使用する以下のパラメーターをSSMに登録します。パラメーター名は任意です
- notionReport-notionAuth : Notion Integration のシークレット
- notionReport-notionDbId : Notion のデータベースID
- notionReport-slackBotToken : Slack のトークン
aws ssm put-parameter --name notionReport-notionAuth --value "xxxxxxxxxx" --type "String" aws ssm put-parameter --name notionReport-notionDbId --value "xxxxxxxxxx" --type "String" aws ssm put-parameter --name notionReport-slackBotToken --value "xxxxxxxxxx" --type "String"
定期的に投稿する Lambda のハンドラーを作成
作成前に Notion SDK の型定義がまだないところがあるので、CDKが生成した .tsconfig
の noImplicitAny
を false
にしておきます
{ "compilerOptions": { // ... 省略 "noImplicitAny": false, // ... 省略 }, "exclude": [ "node_modules", "cdk.out" ] }
src/handler.ts
import { WebClient } from "@slack/web-api"; import { Client } from "@notionhq/client"; import { ScheduledEvent, Context } from "aws-lambda"; import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints"; const notionClient = new Client({ auth: process.env["NOTION_AUTH"], }); const slackClient = new WebClient(process.env["SLACK_BOT_TOKEN"]); const notionDbId = process.env["NOTION_DB_ID"]!; const extractTitleText = (page: PageObjectResponse) => { return page["properties"]["Name"]["title"].reduce( (title, titleObject) => (title += titleObject["plain_text"]), "" ); }; const genYesterdayString = () => { const yesterdayJST = new Date( Date.now() + new Date().getTimezoneOffset() * 60 * 1000 + 9 * 3600 * 1000 - 24 * 3600 * 1000 ); return yesterdayJST.toISOString().split("T")[0]; }; export const handler = async (event: ScheduledEvent, context: Context) => { const yesterday = genYesterdayString(); const response = await notionClient.databases.query({ database_id: notionDbId, filter: { and: [ { property: "作成日", date: { equals: yesterday }, }, ], }, }); const pages = response.results as PageObjectResponse[]; const message = pages .filter((page) => { return page["properties"]["Name"]["title"].length > 0; }) .map((page) => `<${page["url"]}|${extractTitleText(page)}>`) .join("\n"); if (message) { await slackClient.chat.postMessage({ channel: "#test", text: message, }); } return {}; };
Lambda の中身を何点かピックアップして説明します
以下のコードは Notion データベースに、作成日が先日のページをクエリしています。クエリするにはデータベースのIDが必要です
Notion が対応してる全てのクエリは以下のリンクを参考にしてください
const response = await notionClient.databases.query({ database_id: notionDbId, filter: { and: [ { property: "created_time", date: { equals: yesterday }, }, ], }, });
以下のコードは Notion の page オブジェクトからタイトルを取得するコードです。タイトル内にリンクなど装飾が使われて場合に ["Name"]["title"]
の中が複数に分割されてしまうので、それを一つの文字列になるよう連結しています
const extractTitleText = (page: PageObjectResponse) => { return page["properties"]["Name"]["title"].reduce( (title, titleObject) => (title += titleObject["plain_text"]), "" ); };
以下のコードはJST(日本時間)で昨日の日付をISO8601形式で取得しています。ライブラリを使えばもっと簡単にできますが、今回はライブラリを使用せずに対応しました
現在日時から時差を引きその後で日本とUTC(世界標準時)の時差を足して、日付部分のみ文字列として抜き出しています
(ISO8601形式: 2023-03-26T17:07:17.77
から、記号Tで区切り前半の日付部分 2023-03-26
を抜き出し)
const genYesterdayString = () => { const yesterdayJST = new Date( Date.now() + new Date().getTimezoneOffset() * 60 * 1000 + 9 * 3600 * 1000 - 24 * 3600 * 1000 ); return yesterdayJST.toISOString().split("T")[0]; };
CDKを作成する
lib/xxx.ts
import * as cdk from "aws-cdk-lib"; import { Construct } from "constructs"; export class NotionreportStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // Notion と Slack の認証情報等をSSMパラメーターから取得 const notionAuth = cdk.aws_ssm.StringParameter.valueForStringParameter( this, "notionReport-notionAuth" ); const notionDbId = cdk.aws_ssm.StringParameter.valueForStringParameter( this, "notionReport-notionDbId" ); const slackBotToken = cdk.aws_ssm.StringParameter.valueForStringParameter( this, "notionReport-slackBotToken" ); const lambda = new cdk.aws_lambda_nodejs.NodejsFunction(this, "Fn", { runtime: cdk.aws_lambda.Runtime.NODEJS_18_X, entry: "src/handler.ts", environment: { NOTION_AUTH: notionAuth, NOTION_DB_ID: notionDbId, SLACK_BOT_TOKEN: slackBotToken, }, bundling: { sourceMap: true, }, timeout: cdk.Duration.seconds(29), }); // CloudWatch Events で Lambda を定期実行する new cdk.aws_events.Rule(this, "Schedule", { schedule: cdk.aws_events.Schedule.cron({ minute: "55", hour: "0", // UTCなので日本時間だと+9時間される }), targets: [new cdk.aws_events_targets.LambdaFunction(lambda)], }); } }
デプロイ
デプロイして完成です
cdk deploy
まとめ
今回はNotionの日時レポートを Slack に投稿する Lambda を作成しました。思ってたより手軽に作れたので、ぜひ皆様もアレンジして作ってみてください
全てのソースを Github にPushしています。こちらもご参考ください
この記事が誰かの参考になれば幸いです